package role

/*
 * role struct.
 */

import (
	"GameServer/mysql"
	"GameServer/snowflake"
	"GameServer/timer"
	"mnet"
	log "myLog"
	"sync/atomic"
)

const (
	gSaveChanSize = 512
	gOffSaveTime  = 60
	gSaveTime     = 300
)

// role state.
const (
	RoleStateInvalid   int32 = iota // invalid state.
	RoleStateConnected              // connect state
	RoleStateLogin                  // login state
	RoleStateEnterGame              // enter game state
	RoleStateOffLine                // off line state
)

const (
	RoleOPUpdate int32 = iota
	RoleOPInsert
)

// role operation.
type RoleOP struct {
	*Role
	OP int32
}

// Role struct where with "sql" tag should be save to MySQL.
type Role struct {
	UId          uint64        `sql:"uid"`     // uid
	Name         string        `sql:"name"`    // name
	Level        int32         `sql:"level"`   // level
	Account      string        `sql:"account"` // account
	State        atomic.Int32  // state
	Session      *mnet.Session // tcp net session
	saveDBTimeId uint32        // save timer id.
	saveChan     chan *RoleOP  // save chan
	doneChan     chan struct{} // exit save goroutine.
}

// New Role object.
func NewRole() *Role {
	roleObj := &Role{
		Level:    1,
		Session:  nil,
		saveChan: make(chan *RoleOP, gSaveChanSize),
		doneChan: make(chan struct{}),
	}
	id, _ := snowflake.SnowflakeIdWorkerObj.NextId()
	roleObj.SetUid(uint64(id))

	roleObj.Init()
	return roleObj
}

// Init initializes Role.
func (r *Role) Init() {
	r.SetState(RoleStateConnected)
	go func() {
		for opRole := range r.saveChan {
			if opRole != nil {
				// Save vRole to MySQL.
				log.InfoAf("save %+v role", opRole.GetName())
				opRole.doSave(opRole.OP)
			} else {
				// Exit goroutine and signal that the goroutine is exiting.
				close(r.doneChan)
				return
			}
		}
	}()
}

// doSave does save role to mysql.
func (r *Role) doSave(op int32) {
	// do update mysql with r.
	client := mysql.MysqlProxyObj.GetMysqlProxy(mysql.RoleModuleLogicId)
	if op == RoleOPUpdate {
		if _, err := mysql.UpdateStructToMysql(client, "game.role", r); err != nil {
			log.ErrorAf("update %+v error:%+v", r, err)
		} else {
			log.InfoAf("update %+v successfully", r)
		}
	} else if op == RoleOPInsert {
		if _, _, err := mysql.InsertStructToMysql(client, "game.role", r, false); err != nil {
			log.ErrorAf("insert %+v error:%+v", r, err)
		} else {
			log.InfoAf("insert %+v successfully", r)
		}
	}
}

func (r *Role) GetUid() uint64 {
	return r.UId
}
func (r *Role) SetUid(id uint64) {
	r.UId = id
}

func (r *Role) GetName() string {
	return r.Name
}

func (r *Role) GetAccount() string {
	return r.Account
}

// SetSession set tcp session where session maybe nil.
func (r *Role) SetSession(session *mnet.Session) {
	r.Session = session
}

// SendMsg send message with msgId and bin.
func (r *Role) SendMsg(msgId uint16, bin []byte) {
	if r.Session != nil {
		r.Session.SendMsg(msgId, bin)
	}
}

// Send sends binary bin.
func (r *Role) Send(bin []byte) {
	if r.Session != nil {
		r.Session.Send(bin)
	}
}

// SetState set state.
func (r *Role) SetState(state int32) {
	r.State.Store(state)
	if r.GetState() == RoleStateOffLine {
		r.SetOffTimer(gOffSaveTime * 1000)
		timer.TimerMgrObj.GetTimer().KillTimer(r.saveDBTimeId)
	} else if r.GetState() == RoleStateEnterGame {
		r.SetSaveTimer(gSaveTime * 1000)
	}
}

// GetState get state.
func (r *Role) GetState() int32 {
	return r.State.Load()
}

// SetOffTimer sets off line timer.
func (r *Role) SetOffTimer(time int64) {
	timer.TimerMgrObj.GetTimer().AddOnceTimer(timer.TimerMgrObj.GetNextId(), time, true, func(v ...interface{}) {
		if roleObj, ok := v[0].(*Role); ok && roleObj != nil && roleObj.GetState() == RoleStateOffLine {
			// role object is valid then just release it.
			go roleObj.Release()
		}
	}, []interface{}{r})
}

// SetSaveTimer sets save timer.
func (r *Role) SetSaveTimer(time int64) {
	r.saveDBTimeId = timer.TimerMgrObj.GetNextId()
	timer.TimerMgrObj.GetTimer().AddRepeatTimer(r.saveDBTimeId, time, true, func(v ...interface{}) {
		r.Save()
	}, nil)
}

// Save update to mysql.
func (r *Role) Save() {
	if r.GetState() == RoleStateEnterGame {
		log.InfoAf("%+v start to save now.", r)
		r.saveChan <- &RoleOP{r, RoleOPUpdate}
	}
}

// Insert insert into mysql.
func (r *Role) Insert() {
	r.saveChan <- &RoleOP{r, RoleOPInsert}
}

// exit callback, let the save goroutine exit.
func (r *Role) exit() {
	r.saveChan <- nil
}

// Release release role.
func (r *Role) Release() {
	r.Save()

	// Set Invalid state.
	r.SetState(RoleStateInvalid)

	// kill save timer.
	timer.TimerMgrObj.GetTimer().KillTimer(r.saveDBTimeId)

	// remove from roleMgr.
	RoleMgrObj.Remove(r)

	// close save goroutine and wait it to exit.
	r.exit()
	<-r.doneChan

	// release log out.
	log.Infof("%+v is released", r)
}
